Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use data-service sessions endpoint #3296

Merged

Conversation

andre-code
Copy link
Contributor

@andre-code andre-code commented Sep 2, 2024

PR to use data-service /sessions/* endpoint in Renku V2.

What's Included

  • Launch sessions with a custom or global environment
  • Open sessions in the session view
  • See the current session state
  • Start, resume, hibernate, and shut down a session

What's Not Included ‼️

  • Automatic injection of saved cloud storage secrets
  • Displaying an error message when a session has an error

Pending

  • Update e2e tests

/deploy renku=feat-jupyter-free-sessions renku-data-services=release-amaltheas-migration amalthea-sessions=main renku-notebooks=master renku-gateway=1.1.0 extra-values=amalthea-sessions.deployCrd=false

@RenkuBot
Copy link
Contributor

RenkuBot commented Sep 2, 2024

You can access the deployment of this PR at https://renku-ci-ui-3296.dev.renku.ch

@andre-code andre-code changed the base branch from main to andrea/custom-launcher-jupyter-free September 5, 2024 14:58
@andre-code andre-code force-pushed the andrea/use-data-service-sessions-2.0 branch from a1a5479 to 816e188 Compare September 5, 2024 14:59
@andre-code andre-code force-pushed the andrea/use-data-service-sessions-2.0 branch from 816e188 to 1086ab6 Compare September 6, 2024 12:13
@andre-code andre-code force-pushed the andrea/use-data-service-sessions-2.0 branch from 1086ab6 to fbc0695 Compare September 9, 2024 09:33
@andre-code andre-code force-pushed the andrea/custom-launcher-jupyter-free branch from a03efa6 to 0df603e Compare September 19, 2024 08:48
@andre-code andre-code force-pushed the andrea/use-data-service-sessions-2.0 branch from 283294d to deaa61a Compare September 19, 2024 09:14
@andre-code andre-code force-pushed the andrea/custom-launcher-jupyter-free branch from 0df603e to 26364df Compare September 20, 2024 07:55
@andre-code andre-code force-pushed the andrea/use-data-service-sessions-2.0 branch from deaa61a to dbe673e Compare September 20, 2024 08:17
@andre-code andre-code force-pushed the andrea/custom-launcher-jupyter-free branch 3 times, most recently from f059742 to 26d6197 Compare September 24, 2024 06:28
@andre-code andre-code force-pushed the andrea/use-data-service-sessions-2.0 branch from 0522b87 to 57e03e7 Compare September 24, 2024 14:29
Copy link
Member

@leafty leafty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General note: please use the function style for components.

A first batch of comments below:

interface EnvironmentLogsPropsV2 {
name: string;
}
export const EnvironmentLogsV2 = ({ name }: EnvironmentLogsPropsV2) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 70 to 95
if (!logs?.show || logs?.show !== name || !logs) return null;

return (
<Modal
isOpen={!!logs.show}
className="modal-xl"
scrollable={true}
toggle={() => {
toggleLogs(name);
}}
>
<ModalHeader
className="header-multiline"
toggle={() => {
toggleLogs(name);
}}
>
<div>Logs</div>
</ModalHeader>
<ModalBody>
<div className="mx-2">
<SessionLogs fetchLogs={fetchLogs} logs={logs} name={name} />
</div>
</ModalBody>
</Modal>
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this split into another component here? This code can be in the previous component and that saves all the interface and const = lines of code.

@@ -86,13 +86,13 @@ function AddSessionEnvironmentModal({
addSessionEnvironment({
container_image: data.container_image,
name: data.name,
default_url: data.default_url.trim() ? data.default_url : undefined,
description: data.description.trim() ? data.description : undefined,
default_url: data.default_url?.trim() ? data.default_url : undefined,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For here and everywhere else: should we instead have this?

Suggested change
default_url: data.default_url?.trim() ? data.default_url : undefined,
default_url: data.default_url?.trim() || undefined,

Otherwise we send untrimmed strings to the API.

This can be added to a new issue to do later.

Comment on lines 276 to 277
{session?.status?.will_hibernate_at &&
session?.status?.will_hibernate_at?.length > 0 && (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for a length check on strings:

Suggested change
{session?.status?.will_hibernate_at &&
session?.status?.will_hibernate_at?.length > 0 && (
{session?.status?.will_hibernate_at && (

Comment on lines 279 to 276
Please note that paused session are deleted after{" "}
{hibernationThreshold} of inactivity.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not an accurate text copy because session?.status?.will_hibernate_at refers to this specific session. Also the text refers to "DELETE" while the threshold is for "PAUSE".

Comment on lines 262 to 267
const hibernationThreshold = session?.status?.will_hibernate_at
? toHumanRelativeDuration({
datetime: session?.status?.will_hibernate_at,
now,
})
: 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make things correct, it is preferable to use <TimeCaption> instead of computing this. <TimeCaption> supports passing class names and the noCaption attribute to not have the time-caption class.

throw new Error("Unsupported protocol");
} catch (error) {
if (url && !url.includes("://")) {
return `https://${url}`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if this is not a valid URL?

session: SessionV2;
}

export default function SessionIframe({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New issue: re-factor into existing <SessionJupyter> component.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -88,12 +85,15 @@ export default function ShowSessionPage() {
slug: slug ?? "",
});

const { data: sessions, isLoading } = useGetSessionsQuery();
const { data: sessions, isLoading } = useGetSessionsQuery(undefined, {
skip: false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Contributor

@ciyer ciyer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very good! I have some small code suggestions and a question about intent, but those are all quite minor.

We discussed the use of anonymous vs. named functions for the components, and it looks like the project session test is failing, but I'm assuming you are already work on those.

Comment on lines 89 to 90
default_url: data.default_url?.trim() ? data.default_url : undefined,
description: data.description?.trim() ? data.description : undefined,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would this code should actually be this:

Suggested change
default_url: data.default_url?.trim() ? data.default_url : undefined,
description: data.description?.trim() ? data.description : undefined,
default_url: data.default_url?.trim() ? data.default_url.trim() : undefined,
description: data.description?.trim() ? data.description.trim() : undefined,

or maybe even better, introduce a function:

function trimmedOrUndefined(value: string | undefined) {
    return value?.trim() ? value.trim() : undefined;
}

@@ -105,13 +105,13 @@ function UpdateSessionEnvironmentModal({
environmentId: environment.id,
container_image: data.container_image,
name: data.name,
default_url: data.default_url.trim() ? data.default_url : "",
description: data.description.trim() ? data.description : "",
default_url: data.default_url?.trim() ? data.default_url : "",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the comment on AddSessionEnvironmentButton:

function trimmedOrEmpty(value: string | undefined) {
    return value?.trim() ? value.trim() : "";
}

Comment on lines 74 to 75
statusData?.ready_containers && statusData?.total_containers
? `${statusData?.ready_containers} of ${statusData?.total_containers} containers ready`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be safe:

Suggested change
statusData?.ready_containers && statusData?.total_containers
? `${statusData?.ready_containers} of ${statusData?.total_containers} containers ready`
statusData?.ready_containers && statusData?.total_containers
? `${statusData.ready_containers} of ${statusData.total_containers} containers ready`

Comment on lines 279 to 276
Please note that paused session are deleted after{" "}
{hibernationThreshold} of inactivity.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Please note that paused session are deleted after{" "}
{hibernationThreshold} of inactivity.
Please note that paused sessions are deleted after{" "}
{hibernationThreshold} of inactivity.

@@ -577,12 +556,12 @@ function ModifySessionModalContent({
useEffect(() => {
const currentSessionClass = resourcePools
?.flatMap((pool) => pool.classes)
.find((c) => c.id === currentSessionClassId);
.find((c) => `${c.id}` == resource_class_id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be more idiomatic.

Suggested change
.find((c) => `${c.id}` == resource_class_id);
.find((c) => ""+ c.id == resource_class_id);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a javascript "hack" and should not be used. It uses type coercion so this makes code look weird (see things like "1"+1 == 11, etc. ${c.id} is a valid way to convert a number to a string.

Comment on lines 615 to 616
(resource_class_id != null &&
resource_class_id === `${currentSessionClass?.id}`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this logic is used in a couple of places:

Suggested change
(resource_class_id != null &&
resource_class_id === `${currentSessionClass?.id}`)
resourceClassMatchesCurrent(resource_class_id, currentSessionClass)
function resourceClassMatchesCurrent(resourceClassId: string | undefined, currentSessionClass: string) {
    return (resourceClassId != null && resourceClassId === ""+currentSessionClass.id)
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just?

resource_class_id === `${currentSessionClass?.id}

This is all that's needed here (resource_class_id cannot be null-ish and equal a string).

(resource_class_id != null &&
resource_class_id === `${currentSessionClass?.id}`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(resource_class_id != null &&
resource_class_id === `${currentSessionClass?.id}`)
resourceClassMatchesCurrent(resource_class_id, currentSessionClass)

providesTags: (result) =>
result
? [
...result.map(({ name }) => ({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: name is used here, but id below. Is that correct?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name field is the identifier. Maybe that should be changed in the API?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the change has been requested. I will implement this in a future PR once the updates are made in the API.

- use named functions
- refactor working and mount directory handling with optional chaining and fallback to undefined
- remove forced URL generation in ensureHTTPS function when the input is not a valid URL.
- ensure session data is fetched when loading ShowSessionPage
Copy link
Member

@leafty leafty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be good to go after these comments.

@@ -105,15 +105,11 @@ function UpdateSessionEnvironmentModal({
environmentId: environment.id,
container_image: data.container_image,
name: data.name,
default_url: data.default_url.trim() ? data.default_url : "",
description: data.description.trim() ? data.description : "",
default_url: data.default_url?.trim() ? data.default_url : "",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this can be done here as well:

Suggested change
default_url: data.default_url?.trim() ? data.default_url : "",
default_url: data.default_url?.trim() || "",

import { NotebooksHelper } from "../../notebooks";
import { NotebookAnnotations } from "../../notebooks/components/session.types";
import { useGetSessionsQuery as useGetSessionsQueryV2 } from "../../features/sessionsV2/sessionsV2.api";
import "../../notebooks/Notebooks.css";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you keep the CSS imports as the last ones? They semantically different from the other ones, so we should make them stand out. Also keep the associated comment.

- add file comment
- move css import ant the end of the import list
- simplify default_url and description assignment to simplify conditional check

// Required for logs formatting
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you keep this comment here? Otherwise there is no indication that removing the .css import would break something.

- Restore the CSS import comment to prevent the removal of log styles
@andre-code andre-code merged commit 563fdda into andrea/jupyter-free-build Oct 18, 2024
11 of 19 checks passed
@andre-code andre-code deleted the andrea/use-data-service-sessions-2.0 branch October 18, 2024 14:03
@RenkuBot
Copy link
Contributor

Tearing down the temporary RenkuLab deplyoment for this PR.

andre-code added a commit that referenced this pull request Nov 12, 2024
* feat!: support custom env variables in Renku 2.0  (#3227)
* feat: use data-service sessions endpoint (#3296)
* feat: show an error if the docker image identifier is invalid (#3369)

BREAKING CHANGES: requires renku-data-services >= v0.26.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants